﻿using System.Security.Policy;

namespace easy_ccms.core
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using Entities;

    /// <summary>
    /// Basic xml reader.
    /// </summary>
    public class BasicXmlManager
    {
        #region Properties

        /// <summary>
        /// File name.
        /// </summary>
        private string fileName = null;

        /// <summary>
        /// File location.
        /// </summary>
        private string fileLocation = null;

        #endregion

        #region Constructors
        /// <summary>
        /// Create MyXmlReadWriteClass class with your custom File Name
        /// </summary>
        /// <param name="yourFileName">Name of your file.</param>
        public BasicXmlManager(string yourFileName)
        {
            this.fileName = yourFileName;
            this.fileLocation = Environment.CurrentDirectory.ToString();
            CheckFile();
        }

        /// <summary>
        /// Create MyXmlReadWriteClass class with specification of file name and its path
        /// </summary>
        /// <param name="fileName">Accept MyXmlReadWriteClass file name (file.MyXmlReadWriteClass)</param>
        /// <param name="filePath">accept file location path (c:\Users\you)</param>
        public BasicXmlManager(string fileName, string filePath)
        {
            this.fileName = fileName;
            this.fileLocation = filePath;
            CheckFile();
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="BasicXmlManager"/> class from being created.
        /// </summary>
        private BasicXmlManager()
        {
        }

        #endregion

        #region Fields
        /// <summary>
        /// Gets the file.
        /// </summary>
        private string File
        {
            get { return System.Web.HttpContext.Current.Request.MapPath(this.fileLocation +"/"+ this.fileName); }
        }

        #endregion

        #region Get functions

        /// <summary>
        /// Return value of only one string
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>Gets value of 3th level element.</returns>
        public string GetStringValue(string documentElement, string subElementName, string elementName)
        {
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            XmlDocument settingsFile = SettingsFile();
            try
            {
                // XmlNodeList elements = settingsFile.SelectNodes("//fsfaktury/items/item");
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                string value = null;
                foreach (XmlElement element in elements)
                {
                    value = element.InnerText;
                }

                return value;
            }
            catch (XmlException)
            {
                return null;
            }
        }

        /// <summary>
        /// Return array of items
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>collection of values for elements.</returns>
        public List<string> GetStringValueList(string documentElement, string subElementName, string elementName)
        {
            XmlDocument settingsFile = SettingsFile();
            List<string> result = new List<string>();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;

            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);

                result.AddRange(from XmlElement element in elements select element.InnerText);
                return result;
            }
            catch (XmlException)
            {
                return null;
            }
        }

        /// <summary>
        /// Get nodeName Parameters
        /// Support multiple parameters / many elements.
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>
        /// Each element has its unique ElementId
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
        public List<ElementParameterAttributeList> GetElementAttribute(
                                                                        string documentElement, 
                                                                        string subElementName,
                                                                        string elementName)
        {
            XmlDocument settingsFile = SettingsFile();
            List<ElementParameterAttributeList> list = new List<ElementParameterAttributeList>();

            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;

            int itemNumber = 0;
            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null && elements.Count == 0) return null;
                if (elements != null)
                    foreach (
                        XmlElement element in
                            elements.Cast<XmlElement>().TakeWhile(element => element.Attributes.Count != 0))
                    {
                        list.AddRange(from XmlNode atrName in element.Attributes
                                      select new ElementParameterAttributeList
                                                 {
                                                     ElementId = itemNumber,
                                                     ElementName = element.Name,
                                                     ItemName = atrName.Name,
                                                     ItemValue = atrName.Value
                                                 });
                        itemNumber++;
                    }

                return list;
            }
            catch (XmlException)
            {
                return null;
            }
        }

        #endregion

        #region Update functions

        /// <summary>
        /// Update value of MyXmlReadWriteClass element
        /// Cannot be used for multiple values
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns>
        /// Boolean result if update succeded.
        /// </returns>
        public bool UpdateElementValue(
            string documentElement, 
            string subElementName, 
            string elementName,
            string newValue)
        {
            XmlDocument settingsFile = SettingsFile();

            try
            {
                string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
                try
                {
                    XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                    if (elements != null)
                        foreach (XmlNode element in elements)
                        {
                            element.InnerText = newValue;
                        }

                    settingsFile.Save(this.File);
                    return true;
                }
                catch (XmlException)
                {
                    return false;
                }
            }
            catch (ArgumentException)
            {
                return false;
            }
        }

        /// <summary>
        /// Update element attribute
        /// Do not use for multiple elements
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns>Boolean result if update succeded.</returns>
        public bool UpdateElementAttribute(
            string documentElement, 
            string subElementName,
            string elementName,
            string attributeName, 
            string newValue)
        {
            XmlDocument settingsFile = SettingsFile();

            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null)
                    foreach (XmlNode element in elements.Cast<XmlNode>().Where(element => element.Attributes != null))
                    {
                        if (element.Attributes != null) element.Attributes[attributeName].Value = newValue;
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        /// <summary>
        /// Update element attribute with condition on attribute and attribute value
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="searchAttribute">The search attribute.</param>
        /// <param name="searchAttributeValue">The search attribute value.</param>
        /// <returns>
        /// Boolean result if update succeded.
        /// </returns>
        public bool UpdateElementAttribute(
            string documentElement, 
            string subElementName, 
            string elementName,
            string attributeName, 
            string newValue, 
            string searchAttribute,
            string searchAttributeValue)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null)
                    foreach (
                        XmlNode myElement in
                            elements.Cast<XmlNode>().Where(
                                myElement =>
                                myElement.Attributes != null &&
                                myElement.Attributes[searchAttribute].Value == searchAttributeValue))
                    {
                        if (myElement.Attributes != null) myElement.Attributes[attributeName].Value = newValue;
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        #endregion

        #region Add functions

        /// <summary>
        /// Add to element(s) a new attribute value
        /// if attribute allready exists it will update value
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="newAttributeName">New name of the attribute.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns>Boolean result if adding succeded.</returns>
        public bool AddElementAttribute(
            string documentElement, 
            string subElementName, 
            string elementName,
            string newAttributeName, 
            string newValue)
        {
            XmlDocument settingsFile = SettingsFile();

            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null)
                    foreach (XmlNode element in elements)
                    {
                        XmlAttribute itemAtrribute = settingsFile.CreateAttribute(newAttributeName);
                        itemAtrribute.Value = newValue;
                        if (element.Attributes != null) element.Attributes.Append(itemAtrribute);
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        /// <summary>
        /// Create Sub document level nodeName
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="nodeValue">The node value.</param>
        /// <returns>Boolean result if adding succeded.</returns>
        public bool AddNode(string documentElement, string nodeName, string nodeValue)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement;
            try
            {
                XmlElement childElement = settingsFile.CreateElement(nodeName);
                if (nodeValue != null) childElement.InnerText = nodeValue;

                XmlNode parentNode = settingsFile.SelectSingleNode(locationElement);
                if (parentNode != null) parentNode.AppendChild(childElement);

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        /// <summary>
        /// Create new nodeName in 3th level
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="nodeName">Name of the node.</param>
        /// <param name="nodeValue">The node value.</param>
        /// <returns>Boolean result if adding succeded.</returns>
        public bool AddNode(string documentElement, string subElementName, string nodeName, string nodeValue)
        {
            XmlDocument settingsFile = SettingsFile();

            string locationElement = "//" + documentElement + "/" + subElementName;
            try
            {
                XmlElement childElement = settingsFile.CreateElement(nodeName);
                if (nodeValue != null) childElement.InnerText = nodeValue;

                XmlNode parentNode = settingsFile.SelectSingleNode(locationElement);
                if (parentNode != null) parentNode.AppendChild(childElement);

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }
        #endregion

        #region Remove functions
        /// <summary>
        /// Remove element from sub nodeName
        /// Do not use on multiple items
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>Boolean result if remove succeded.</returns>
        public bool RemoveNode(string documentElement, string subElementName, string elementName)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            string locationParentElement = "//" + documentElement + "/" + subElementName;
            try
            {
                XmlNode node = settingsFile.SelectSingleNode(locationElement);
                XmlNode parentNode = settingsFile.SelectSingleNode(locationParentElement);

                if (parentNode != null && node != null) parentNode.RemoveChild(node);
                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
            catch (NullReferenceException)
            {
                return false;
            }
        }

        /// <summary>
        /// Remove all elements inside sub nodeName
        /// For multiple elements
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <returns>Boolean result if remove succeded.</returns>
        public bool RemoveNodes(string documentElement, string subElementName, string elementName)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            string locationParentElement = "//" + documentElement + "/" + subElementName;
            try
            {
                XmlNodeList node = settingsFile.SelectNodes(locationElement);
                XmlNode parentNode = settingsFile.SelectSingleNode(locationParentElement);

                if (node != null)
                    foreach (XmlNode n in from XmlNode n in node where parentNode != null select n)
                    {
                        if (parentNode != null) parentNode.RemoveChild(n);
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
            catch (NullReferenceException)
            {
                return false;
            }
        }

        /// <summary>
        /// Remove nodeName by Attribute
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="attributeValue">The attribute value.</param>
        /// <returns>Boolean result if remove succeded.</returns>
        public bool RemoveNodeByAttribute(string documentElement, string subElementName, string elementName, string attributeName, string attributeValue)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            string locationParentElement = "//" + documentElement + "/" + subElementName;
            try
            {
                XmlNode parentNode = settingsFile.SelectSingleNode(locationParentElement);
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null)
                    foreach (
                        XmlNode element in
                            elements.Cast<XmlNode>().Where(
                                element =>
                                element.Attributes != null && element.Attributes[attributeName].Value == attributeValue))
                    {
                        if (parentNode != null) parentNode.RemoveChild(element);
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        /// <summary>
        /// Remove attribute by name from all items found
        /// Do not use on multiple items
        /// </summary>
        /// <param name="documentElement">The document element.</param>
        /// <param name="subElementName">Name of the sub element.</param>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="remAttributeName">Name of the rem attribute.</param>
        /// <returns>Boolean result if remove succeded.</returns>
        public bool RemoveElementAttribute(string documentElement, string subElementName, string elementName, string remAttributeName)
        {
            XmlDocument settingsFile = SettingsFile();
            string locationElement = "//" + documentElement + "/" + subElementName + "/" + elementName;
            try
            {
                XmlNodeList elements = settingsFile.SelectNodes(locationElement);
                if (elements != null)
                    foreach (XmlElement element in elements)
                    {
                        element.RemoveAttribute(remAttributeName);
                    }

                settingsFile.Save(this.File);
                return true;
            }
            catch (XmlException)
            {
                return false;
            }
        }

        /// <summary>
        /// Settingses the file.
        /// </summary>
        /// <returns>Load settings file to memory.</returns>
        private XmlDocument SettingsFile()
        {
            XmlDocument settingsFile = new XmlDocument();
            settingsFile.Load(this.File);
            return settingsFile;
        }

        /// <summary>
        /// Check whether file exists
        /// </summary>
        /// <exception cref="FileNotFoundException">Throws exception with location of file that has not been found</exception>
        private void CheckFile()
        {
            
            if (!System.IO.File.Exists(this.File))
            {
                throw new FileNotFoundException(this.File);
            }
        }
        #endregion
    }
}
